#ifndef AVALON_SERVICELOCATORCLIENT_HPP
#define AVALON_SERVICELOCATORCLIENT_HPP
#include "Avalon/IO/Connection.hpp"
#include "Avalon/IO/ConnectionState.hpp"
#include "Avalon/ServiceLocator/DirectoryEntry.hpp"
#include "Avalon/ServiceLocator/Directory.hpp"
#include "Avalon/ServiceLocator/ServiceLocatorServices.hpp"
#include "Avalon/Services/ServiceNode.hpp"
#include "Avalon/SignalHandling/TaskSignalHandler.hpp"

namespace Avalon {
namespace ServiceLocator {

  /*! \class ServiceLocatorClient
   *  \brief Client used to locate services on a service network.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  class ServiceLocatorClient : public IO::Connection {
    public:

      //! Signals whether a service is available.
      /*!
        \param serviceEntry The service entry.
        \param available <code>true</code> iff the service is available.
      */
      typedef boost::signals2::signal<void (const ServiceEntry&, bool)>
        ServiceSignal;

      //! Constructs a ServiceLocatorClient.
      /*!
        \param channel The Channel that connects to the ServiceLocatorServer.
        \param dataShuttleFactory The type of serialization to use.
        \param timeout The Connection timeout.
      */
      ServiceLocatorClient(IO::Channel* channel,
        Serialization::DataShuttleFactory* dataShuttleFactory,
        boost::posix_time::time_duration timeout =
        Services::ServiceNode::TIMEOUT);

      virtual ~ServiceLocatorClient();

      //! Returns the Tag of the Account that's logged in.
      DirectoryEntry::Tag GetAccountTag();

      //! Returns the raw session id.
      std::string GetSessionId();

      //! Encrypts the session id.
      /*!
        \param key The encryption key.
        \return The session id encrypted using the specified encryption
                <i>key</i>.
      */
      std::string GetEncryptedSessionId(unsigned int key);

      //! Logs onto the ServiceLocatorServer.
      /*!
        \param username The username.
        \param password The password.
      */
      void Login(const std::string& username, const std::string& password);

      //! Authenticates a session.
      /*!
        \param sessionId An encoded session id.
        \param key The encryption key used to encode the <i>sessionId</i>.
        \return The Tag of the Account using the specified <i>sessionId</i>.
      */
      DirectoryEntry::Tag AuthenticateSession(const std::string& sessionId,
        unsigned int key);

      //! Locates a service.
      /*!
        \param name The name of the service to locate.
        \return The list of services that were located.
      */
      std::vector<ServiceEntry> Locate(const std::string& name);

      //! Subscribes to service notifications.
      /*!
        \param name The name of the service to subscribe to.
        \return A connection to service notifications for services with the
                specified <i>name</i>.
      */
      boost::signals2::connection Subscribe(const std::string& name,
        const ServiceSignal::slot_type& slot);

      //! Subscribes to service notifications.
      /*!
        \param name The name of the service to subscribe to.
        \param services [out] The list of services currently available.
        \return A connection to service notifications for services with the
                specified <i>name</i>.
      */
      boost::signals2::connection Subscribe(const std::string& name,
        const ServiceSignal::slot_type& slot,
        std::vector<ServiceEntry>* services);

      //! Registers a service.
      /*!
        \param name The name of the service.
        \param details Any additional details about the service.
        \return The ServiceEntry corresponding to the registered service.
      */
      ServiceEntry Register(const std::string& name,
        const std::string& details);

      //! Creates an Account.
      /*!
        \param name The name of the Account.
        \param directory The Account's directory.
        \param password The Account's password.
        \return The Tag representing the new Account.
      */
      DirectoryEntry::Tag CreateAccount(const std::string& name,
        const DirectoryEntry::Tag& directory, const std::string& password);

      //! Loads a DirectoryEntry.
      /*!
        \param id The id of the DirectoryEntry to load.
        \return The DirectoryEntry with the specified <i>id</i>.
      */
      DirectoryEntry* LoadDirectoryEntry(unsigned int id);

      //! Loads the parent Directory of a DirectoryEntry.
      /*!
        \param id The id of the DirectoryEntry whose parent is to be loaded.
        \return The parent of the DirectoryEntry with the specified <i>id</i>.
      */
      Directory* LoadParentDirectory(unsigned int id);

      //! Creates a Directory.
      /*!
        \param name The name of the directory.
        \param parent The parent directory.
      */
      void CreateDirectory(const std::string& name,
        const DirectoryEntry::Tag& parent);

      //! Moves a DirectoryEntry.
      /*!
        \param tag The Tag of the DirectoryEntry to move.
        \param directory The Directory to move the DirectoryEntry to.
      */
      void MoveEntry(const DirectoryEntry::Tag& tag,
        const DirectoryEntry::Tag& directory);

      //! Deletes an DirectoryEntry.
      /*!
        \param tag The Tag of the entry to delete.
      */
      void DeleteEntry(const DirectoryEntry::Tag& tag);

      //! Sets an Account's Permissions for a DirectoryEntry.
      /*!
        \param account The Account Tag whose Permissions are to be set.
        \param entry The Tag of the Entry to set the <i>account</i>'s
               Permissions for.
        \param permissions The Permissions to grant to the <i>account</i> for
               the specified <i>entry</i>.
      */
      void SetPermissions(const DirectoryEntry::Tag& account,
        const DirectoryEntry::Tag& entry, Directory::Permission permissions);

      virtual bool IsConnected();

      virtual void Close();

      virtual boost::signals2::connection ConnectClosedSignal(
        const ClosedSignal::slot_type& slot);

      virtual boost::signals2::connection ConnectClosedSignal(
        const ClosedSignal::slot_type& slot, bool* isConnected);

    private:
      boost::mutex m_mutex;
      struct ServiceSignalEntry;
      boost::scoped_ptr<IO::Channel> m_channel;
      IO::ConnectionState m_channelState;
      bool m_isConnected;
      Services::ServiceNode m_service;
      DirectoryEntry::Tag m_accountTag;
      std::string m_sessionId;
      std::map<unsigned int, DirectoryEntry*> m_entries;
      std::map<unsigned int, Directory*> m_parentEntries;
      std::map<std::string, ServiceSignalEntry*> m_serviceEntries;
      boost::condition_variable m_closedCondition;
      ClosedSignal m_closedSignal;
      SignalHandling::TaskSignalHandler m_signalHandler;

      boost::signals2::connection InternalSubscribe(const std::string& name,
        const ServiceSignal::slot_type& slot,
        std::vector<ServiceEntry>* services,
        boost::unique_lock<boost::mutex>& lock);
      void InternalClose();
      DirectoryEntry* InternalLoadDirectoryEntry(unsigned int id,
        boost::unique_lock<boost::mutex>& lock);
      void Shutdown();
      void OnConnectionClosed();
      void OnServiceAvailability(IO::Channel* channel,
        const ServiceEntry& service, bool available);
      void OnDirectoryEntryMoved(IO::Channel* channel,
        const DirectoryEntry::Tag& entry,
        const DirectoryEntry::Tag& targetDirectory, bool added);
  };
}
}

#endif // AVALON_SERVICELOCATORCLIENT_HPP
